/**
 ****************************************************************************************************
 * @file        key.c
 * @author      正点原子团队(ALIENTEK)
 * @version     V1.0
 * @date        2020-04-20
 * @brief       按键输入 驱动代码
 * @license     Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
 ****************************************************************************************************
 * @attention
 *
 * 实验平台:正点原子 STM32F103开发板
 * 在线视频:www.yuanzige.com
 * 技术论坛:www.openedv.com
 * 公司网址:www.alientek.com
 * 购买地址:openedv.taobao.com
 *
 * 修改说明
 * V1.0 20200420
 * 第一次发布
 *
 ****************************************************************************************************
 */

#include "key.h"
#include "delay.h"
#include "stm32f4xx_hal.h"
/**
 * @brief       按键初始化函数
 * @param       无
 * @retval      无
 */
void key_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;
    KEY0_GPIO_CLK_ENABLE();                                     /* KEY0时钟使能 */
    KEY1_GPIO_CLK_ENABLE();                                     /* KEY1时钟使能 */
    KEY2_GPIO_CLK_ENABLE();                                     /* KEY2时钟使能 */
    WKUP_GPIO_CLK_ENABLE();                                     /* WKUP时钟使能 */

    gpio_init_struct.Pin = KEY0_GPIO_PIN;                       /* KEY0引脚 */
    gpio_init_struct.Mode = GPIO_MODE_INPUT;                    /* 输入 */
    gpio_init_struct.Pull = GPIO_PULLUP;                        /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;              /* 高速 */
    HAL_GPIO_Init(KEY0_GPIO_PORT, &gpio_init_struct);           /* KEY0引脚模式设置,上拉输入 */

    gpio_init_struct.Pin = KEY1_GPIO_PIN;                       /* KEY1引脚 */
    gpio_init_struct.Mode = GPIO_MODE_INPUT;                    /* 输入 */
    gpio_init_struct.Pull = GPIO_PULLUP;                        /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;              /* 高速 */
    HAL_GPIO_Init(KEY1_GPIO_PORT, &gpio_init_struct);           /* KEY1引脚模式设置,上拉输入 */

    gpio_init_struct.Pin = KEY2_GPIO_PIN;                       /* KEY2引脚 */
    gpio_init_struct.Mode = GPIO_MODE_INPUT;                    /* 输入 */
    gpio_init_struct.Pull = GPIO_PULLUP;                        /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;              /* 高速 */
    HAL_GPIO_Init(KEY2_GPIO_PORT, &gpio_init_struct);           /* KEY2引脚模式设置,上拉输入 */

    gpio_init_struct.Pin = WKUP_GPIO_PIN;                       /* WKUP引脚 */
    gpio_init_struct.Mode = GPIO_MODE_INPUT;                    /* 输入 */
    gpio_init_struct.Pull = GPIO_PULLUP;                      /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;              /* 高速 */
    HAL_GPIO_Init(WKUP_GPIO_PORT, &gpio_init_struct);           /* WKUP引脚模式设置,下拉输入 */

}

/**
 * @brief       按键扫描函数
 * @note        该函数有响应优先级(同时按下多个按键): WK_UP > KEY2 > KEY1 > KEY0!!
 * @param       mode:0 / 1, 具体含义如下:
 *   @arg       0,  不支持连续按(当按键按下不放时, 只有第一次调用会返回键值,
 *                  必须松开以后, 再次按下才会返回其他键值)
 *   @arg       1,  支持连续按(当按键按下不放时, 每次调用该函数都会返回键值)
 * @retval      键值, 定义如下:
 *              KEY0_PRES, 1, KEY0按下
 *              KEY1_PRES, 2, KEY1按下
 *              KEY2_PRES, 3, KEY2按下
 *              WKUP_PRES, 4, WKUP按下
 */
uint8_t key_scan(uint8_t mode)
{
    static uint8_t key_up = 1;  /* 按键按松开标志 */
    uint16_t keyval = 0;

    if (mode) key_up = 1;       /* 支持连按 */

    if (key_up && (KEY0 == 0 || KEY1 == 0 || KEY2 == 0 || WK_UP == 0))  /* 按键松开标志为1, 且有任意一个按键按下了 */
    {
        delay_ms(10);           /* 去抖动 */
        key_up = 0;

        if (KEY0 == 0)  keyval = KEY0_PRES;

        if (KEY1 == 0)  keyval = KEY1_PRES;

        if (KEY2 == 0)  keyval = KEY2_PRES;

        if (WK_UP == 0) keyval = WKUP_PRES;
    }
    else if (KEY0 == 1 && KEY1 == 1 && KEY2 == 1 && WK_UP == 1) /* 没有任何按键按下, 标记按键松开 */
    {
        key_up = 1;
    }

    return keyval;              /* 返回键值 */
}

/**
 * @brief 扫描并返回被按下的按键值，同时支持连按和连按加速功能
 * @param mode 是否支持连按，0表示不支持，非0表示支持
 * @note 通过调用次数来决定连按的次数
 * @note 要修改连按触发的初始频率，修改KEY_CONTINUE_FRE宏定义
 * @note 连续触发3次就会加速，速度分为4级，每级触发频率是上一级的四分之一
 * @note 要加大加速度请将3改小或者将速度分级改少
 * @return 被按下的按键值
 */
uint8_t key_scan_plus(uint8_t mode)
{
    static uint8_t key_up = 1;  /* 按键释放标志 */
    uint16_t keyval = 0;  /* 按键值 */

    #define KEY_CONTINUE_FRE 200  /* 连按触发频率初始值 */
    static uint16_t key_continue = 0;  /* 连按计数器 */
    static uint16_t key_continueFrequency = KEY_CONTINUE_FRE;  /* 连按触发频率 */
    static uint16_t key_accelerate = 0;  /* 连按加速计数器 */

    if (mode) {  /* 如果支持连按 */
        key_continue++;  /* 连按计数器增加 */
        if (key_continue > key_continueFrequency) {  /* 如果连按计数器超过触发频率 */
            key_up = 1;  /* 标记按键已释放 */
            key_continue = 0;  /* 重置连按计数器 */
        }
    }

    if (key_up && (KEY0 == 0 || KEY1 == 0 || KEY2 == 0 || WK_UP == 0)) {  /* 如果按键已释放且有按键被按下 */
        delay_ms(10);  /* 去抖动 */
        key_up = 0;  /* 标记按键已按下 */

        key_accelerate++;  /* 连按加速计数器增加 */
        if (key_accelerate > 3) {  /* 如果连按加速计数器超过3 */
            key_continueFrequency -= KEY_CONTINUE_FRE/4;  /* 减少连按触发频率 */
            if(key_continueFrequency < KEY_CONTINUE_FRE/4 || key_continueFrequency & 0x8000)  /* 如果连按触发频率过小或者减小到负数 */
                key_continueFrequency = KEY_CONTINUE_FRE/4;  /* 重置连按触发频率为初始值的四分之一 */
            key_accelerate = 0;  /* 重置连按加速计数器 */
        }

        if (KEY0 == 0)  keyval = KEY0_PRES;  /* 如果KEY0被按下，设置按键值为KEY0_PRES */
        if (KEY1 == 0)  keyval = KEY1_PRES;  /* 如果KEY1被按下，设置按键值为KEY1_PRES */
        if (KEY2 == 0)  keyval = KEY2_PRES;  /* 如果KEY2被按下，设置按键值为KEY2_PRES */
        if (WK_UP == 0) keyval = WKUP_PRES;  /* 如果WK_UP被按下，设置按键值为WKUP_PRES */
    }
    else if (KEY0 == 1 && KEY1 == 1 && KEY2 == 1 && WK_UP == 1) {  /* 如果没有按键被按下 */
        key_up = 1;  /* 标记按键已释放 */
        if(mode){  /* 如果支持连按 */
            key_continueFrequency = KEY_CONTINUE_FRE;  /* 重置连按触发频率为初始值 */
            key_accelerate = 0;  /* 重置连按加速计数器 */
        }
    }

    return keyval;  /* 返回按键值 */
    #undef KEY_CONTINUE_FRE
}

/// @brief 扫描并返回被按下的按键值，同时支持连按和连按加速功能
/// @param mode 1表示支持连按，0表示不支持连按
/// @param gl_time 全局时间
/// @note 与上面的plus版本的区别是，这个版本的连按触发时间间隔是固定的，是用全局时间来计算的，上面那个是用调用的次数来计算的
/// @note 连续触发3次就会加速，速度分为4级，每级触发频率是上一级的二分之一
/// @note 连按触发周期还不会小于两次调用的时间差，即要调用才会触发
/// @return 键值
uint8_t key_scan_plusplus(uint8_t mode,uint32_t gl_time)
{
    static uint8_t key_up = 1;  /* 按键释放标志 */
    uint16_t keyval = 0;  /* 按键值 */
    #define CONTINUE_TIME 500 //连按触发时间间隔
    //计算方法：1/(gl_time_MIN*continueTime),比如这里的全局时间最小间隔为1ms,连按触发时间间隔为500,即1/(0.001*500)=2次/s
    static uint32_t continueTime = CONTINUE_TIME;  
    static uint32_t lastTime = 0;
    uint32_t interval = gl_time - lastTime;//两次调用的时间间隔
    static uint16_t key_continue = 0;  /* 连按计数器 */
    if (mode) {
        if(interval>=continueTime)
        {
            key_continue++;  /* 连按计数器增加 */
            if (key_continue > 3) {  /* 连按的次数大于3 */
                continueTime/=2; //减小连按的触发时间间隔
                if(continueTime<(CONTINUE_TIME/8))
                {
                    continueTime = CONTINUE_TIME/8;
                }
                key_continue = 0;  /* 重置连按计数器 */
            }
            lastTime = gl_time;
            key_up = 1;
        }

    }       /* 支持连按 */

    if (key_up && (KEY0 == 0 || KEY1 == 0 || KEY2 == 0 || WK_UP == 0))  /* 按键松开标志为1, 且有任意一个按键按下了 */
    {
        delay_ms(10);           /* 去抖动 */
        key_up = 0;

        if (KEY0 == 0)  keyval = KEY0_PRES;

        if (KEY1 == 0)  keyval = KEY1_PRES;

        if (KEY2 == 0)  keyval = KEY2_PRES;

        if (WK_UP == 0) keyval = WKUP_PRES;
    }
    else if (KEY0 == 1 && KEY1 == 1 && KEY2 == 1 && WK_UP == 1) /* 没有任何按键按下, 标记按键松开 */
    {
        key_up = 1;
        lastTime = 0;  /* 重置上次按键时间 */
        continueTime = CONTINUE_TIME;  /* 重置连按触发时间间隔 */
    }

    return keyval;              /* 返回键值 */
    #undef CONTINUE_TIME
}
uint8_t key_scan_noDelay(uint8_t mode,uint32_t gl_time){
      static uint8_t key_up = 1;  /* 按键释放标志 */
    uint16_t keyval = 0;  /* 按键值 */

    static uint8_t delayFlag =0;
    static uint32_t delayTime =0;

    #define CONTINUE_TIME 500 //连按触发时间间隔
    //计算方法：1/(gl_time_MIN*continueTime),比如这里的全局时间最小间隔为1ms,连按触发时间间隔为300,即1/(0.001*30)=3.3次/s
    static uint32_t continueTime = CONTINUE_TIME;  
    static uint32_t lastTime = 0;
    uint32_t interval = gl_time - lastTime;//两次调用的时间间隔
    static uint16_t key_continue = 0;  /* 连按计数器 */

    if(delayFlag==1)
    {
        if(gl_time>delayTime+10) delayFlag=0;
        else return 0;
    }
    if (mode) {
        if(interval>=continueTime)
        {
            key_continue++;  /* 连按计数器增加 */
            if (key_continue > 2) {  /* 连按的次数大于3 */
                continueTime/=3; //减小连按的触发时间间隔
                if(continueTime<(CONTINUE_TIME/9))
                {
                    continueTime = CONTINUE_TIME/9;
                }
                key_continue = 0;  /* 重置连按计数器 */
            }
            lastTime = gl_time;
            key_up = 1;
        }

    }       /* 支持连按 */

    if (key_up && (KEY0 == 0 || KEY1 == 0 || KEY2 == 0 || WK_UP == 0))  /* 按键松开标志为1, 且有任意一个按键按下了 */
    {
        
        if(delayTime==0) {delayTime=gl_time;delayFlag=1;return 0;}
        key_up = 0;

        if (KEY0 == 0)  keyval = KEY0_PRES;

        if (KEY1 == 0)  keyval = KEY1_PRES;

        if (KEY2 == 0)  keyval = KEY2_PRES;

        if (WK_UP == 0) keyval = WKUP_PRES;

        delayTime=0;
    }
    else if (KEY0 == 1 && KEY1 == 1 && KEY2 == 1 && WK_UP == 1) /* 没有任何按键按下, 标记按键松开 */
    {
        key_up = 1;
        lastTime = 0;  /* 重置上次按键时间 */
        continueTime = CONTINUE_TIME;  /* 重置连按触发时间间隔 */
    }

    return keyval;              /* 返回键值 */  
    #undef CONTINUE_TIME
}
















